iT邦幫忙

2023 iThome 鐵人賽

DAY 18
0
Modern Web

從0開始的的Angular站台架設-Stnadalone 系列 第 18

D17 Rxjs的Effect與Proxy API串接與如何透過Service管理(上)

  • 分享至 

  • xImage
  •  

Action => Reducer => Selector => Subscribe

這是一個多麼自洽且美麗的一個流程,基本上無處安置第三者的存在

但假如我發出一個Action之後,我有後續的動作要做呢?

我們雖然可以透過Reducer的訂閱在去重複觸發新的Action,但這就像是遂發槍一樣,打一發子彈出去,自己填入彈丸與火藥之後再打一發出去

不是說不行,那怕是現代也都還有中世紀全武裝格鬥大賽嘛

但就不太符合我們使用Rxjs追求資料流管理的目的了,我們為了未來可以管理方便,我們必須盡可能的減少巢狀Subscribe的狀況

所以為了讓我們可以快速Action以及後續相關的Action做擊發,我們要使用Effect來讓原本的單發遂發槍進化成半自動步槍啦

Effect最為常見的環境是API的Request發起成功後續狀態,成功導入讀寫API成功的Action_Success,失敗的則導入Action_Fail的流程之中
Effect同樣需要知道是哪一Action進行擊發的,總體而言架構與Reducer接近,以下是範例程式碼:


 import { Injectable, inject } from '@angular/core';
 import { UserActions } from '../actions';
 import { catchError, concatMap, map, of } from 'rxjs';
 import { Actions, createEffect, ofType } from '@ngrx/effects';
 import { UserInfoService } from 'src/app/shared/service/user-info.service';
 import { UserLoginInfo, userLoginInfo } from '../../models';

 @Injectable()
 export class UserEffects {
   private actions$ = inject(Actions);
   private userInfoService = inject(UserInfoService);
   
   /**
    * 登入
    */

   userLogin$ = createEffect(() => {
     return this.actions$.pipe(
       ofType(UserActions.USER_LOGIN),
       concatMap((action) =>
         this.userInfoService.login(action.userLogin).pipe(
           map((result: UserLoginInfo) => {
             if (result.isSuccess) {
               return UserActions.USER_LOGIN_SUCCESS({
                 userLoginStatus: true,
                 userLoginDetail: result,
               });
             } else {
               return UserActions.USER_LOGIN_FAIL({
                 userLoginStatus: false,
                 userLoginDetail: result,
               });
             }
           }),
           catchError(() =>
             of(
               UserActions.USER_LOGIN_FAIL({
                 userLoginStatus: false,
                 userLoginDetail: userLoginInfo,
               })
             )
           )
         )
       )
     );
   });
 }

首先,我們需要使用createEffect來進行一個EffectuserLogin$實作,然後把它開始監聽它
監聽的位置一樣放在app.config.ts之中


import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './core/routes/app.routes';
import { provideStore } from '@ngrx/store';
import { reducer } from './core/store/reducers';
import { layoutReducerKey } from './core/store/reducers/layout.reducers';
import { effects } from './core/store/effects';
import { provideEffects } from '@ngrx/effects';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideStore({
      [layoutReducerKey]: reducer[layoutReducerKey],
    }),
    provideEffects(effects),
  ],

};

假如這邊沒有effect在node_module中找不到的話,恩,很正常
為了不讓整個rxjs太肥,@ngrx/effects 也是先被切割出去的,所以下個安裝指令吧

npm i @ngrx/effects

這樣子我就可以正常將Effect給啟用起來了

相信有朋友看到我用一個userInfoService的Service來做使用,這個Service究竟是什麼呢?

Service主要是整個系統都可以進行引用的方法,來做整個系統方法的抽離與集中管理

而取得API與發起Action等行為我們其實都可以在這個地方進行實作,這邊也是整個系統與頁務邏輯最為貼近的地方

包括且不限於外部API乃至於我們自己的layoutStatus都可以在這個地方進行引用

那我們這邊就開始嘗試一個API的串結吧


首先API的串接我們是需要一個測試用的API,我這邊也不太可能在教大家用Node.js或是python flask來簡單做一個測試用後端,那樣子30天的內容就太多了

所以我們這邊就用https://dummyapi.io/這個網頁服務來串接api

先申請一個帳號之後 我們就可以透過它的Doc來進行API的串接練習了~

先在shared的層級之中建立一個Service的資料夾並在下面分別建立兩個Service叫做user-info-management.service.ts與http-management.service.ts

前者用來進行user相關的全域管理,後者進行http的再格式化管理,後者的存在需要是因為我們可能因為需要打的API在不同的服務站台上,導致我們的Header需要另外做客製化

首先我們先來個基礎的GETPOST模板方便我們做不同的API客製化

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class HttpService {
  private http = inject(HttpClient);
  private get<T>(path: string, headers?: HttpHeaders, params?: any): Observable<T> {
    return this.http.get<T>(path, {
      params: params,
      headers: headers,
    });
  }
  private post<T>(
    path: string,
    headers: HttpHeaders,
    params: any = null,
    body: any,
    responseType?: any
  ): Observable<T> {
    return this.http.post<T>(path, body, {
      params: params,
      headers: headers,
      responseType: responseType,
    });
  }
}

另外Http也是需要透過provideHttpClient載入到app.config.js,不然是沒有辦法開起一個對外的聯繫管道

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './core/routes/app.routes';
import { provideStore } from '@ngrx/store';
import { reducer } from './core/store/reducers';
import { layoutReducerKey } from './core/store/reducers/layout.reducers';
import { provideHttpClient } from '@angular/common/http';
import { effects } from './core/store/effects';
import { provideEffects } from '@ngrx/effects';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideStore({
      [layoutReducerKey]: reducer[layoutReducerKey],
    }),
    provideHttpClient(),
    provideEffects(effects),
  ],
};

接下來到了神奇的環節了,假如我們直接給一個localhost url進行api資料要求存取的話,就算後端跟我們在同一個站台(ip位址是相同的),也會因為js的實作是下載到客戶端上,你這時候宣告的localhost是取不到的,用戶的電腦以及手機平板並沒有啟用一個Serve並開啟一個接口叫做localhost:8080

這就是Proxy的功用所在,他可以代替我們進行轉值,將需求從**localhost:8080**實際導致我們的同源站台127.156.1.233

{
  "/originApi": {
    "target": "https://localhost:8080",
    "secure": false,
    "changeOrigin": false,
    "pathRewrite": {
      "^/originApi": "api"
    }
  },
  "/dummyApi": {
    "target": "https://dummyapi.io/data/v1/",
    "secure": true,
    "changeOrigin": true,
    "pathRewrite": {
      "^/dummyApi": ""
    }
  }
}

secure主要是有沒有SSL憑證,而假如是非同源(not localhost)的話我們就可以用changeOrigin為true來連外部API

而這個設定黨我們要放在angular.json之中serve的option進行系統初始化的載入,所以proxy一旦有變更必須要再重新ng serve

"serve": {
    "builder": "@angular-devkit/build-angular:dev-server",
    "configurations": {
        "production": {
            "browserTarget": "E_Shopping_System:build:production"
        },
        "development": {
            "browserTarget": "E_Shopping_System:build:development"
        }
    },
    "defaultConfiguration": "development",
        "options": {
            "port": 6837,
            "browserTarget": "todo:build",
            "proxyConfig": "src/proxy.config.json" //<--這裡
          }
},

接下來回到我們的httpService,我自己的習慣是會RESTful API的方式重新命名外部來源的RESTful
所以長相如下

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { AppSettings } from 'src/app/core/settings/app.settings';

@Injectable({
  providedIn: 'root',
})
export class HttpService {
  private http = inject(HttpClient);
  private get<T>(path: string, headers?: HttpHeaders, params?: any): Observable<T> {
    return this.http.get<T>(path, {
      params: params,
      headers: headers,
    });
  }
  
  getFormDummy<T>(path: string, params?: any): Observable<T> {
    const headers = new HttpHeaders({ 'app-id': AppSettings.DUMMY_API_KEY });
    return this.get(path, params, headers);
  }
}

我原本想要將這個文章內容一口氣講完的,但發現光是說一下串接API就有點滿了,一時半會的說不完QQ,所以我們將會把後續的ActionEffect的實作放在明天講講


上一篇
D16 系統層級的Angular Standalon資料管理,RxJs、Store、Action、Effect、Reducer(3)
下一篇
D18 Rxjs的Effect與Proxy API串接與如何透過Service管理(下)
系列文
從0開始的的Angular站台架設-Stnadalone 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言